Skip to main content
  1. Writing/

Behind The Spotify Wrapped API Scenes

·702 words
Table of Contents

Every year (unless you’re one of those Apple Music people) music fans rejoice to get their Spotify Wrapped, or - the musical year in review. It’s a fun way to explore the most frequently listened to songs and artists. And every year up until this one, if memory serves me right, the experience could be viewed in the browser. And then, I paid the site a visit in the year 2021.

Screenshot of the Spotify Wrapped site

Well that’s a bummer, but I wonder if there is some special technical magic that they absolutely need to do client-side that cannot be rendered in the browser? Surely this is not just an elaborate scheme to get people to engage with the mobile app.

To roleplay as Sherlock Holmes, I decided to count on my trusted friends:

  1. An iPhone device, where the Spotify app was installed.
  2. Fiddler on my Windows developer box (or mitmproxy if you are a macOS person).
  3. A self-signed certificate that allows me to decrypt local HTTPS traffic.
  4. A lunch break.

As I was trying to explore this, I was hoping that Spotify does not do certificate pinning, since that would foil my plans. Spoiler alert - they don’t. But as it turns out, I was quite naive in assuming that things will just run right away. After configuring the Fiddler certificate with an iOS 15 device, I still could not capture any HTTPS traffic. It threw me off a bit because I was thinking that maybe between me deciding to write this blog post and putting my hands on the keyboard Spotify actually implemented certificate pinning. Something about Fiddler didn’t work well with iOS despite all the trust settings, so I switched to using mitmproxy inside WSL.

After a bit of configuration, I was up and running, and inspecting traffic like usual:

Screenshot of mitmproxy inside Windows Subsystem for Linux

First thing that caught my eye was a request to the CDN that was pulling something of type video/MP2T - could I have struck gold that fast? I quickly grabbed the URL, which had the form of:

https://video-akpcw-cdn-spotify-com.akamaized.net/segments/v1/origins/{FIRST_ID}/sources/{SOME_ID}/encodings/{ANOTHER_ID}/profiles/17/4.ts?token=SOME_TOKEN&token_ak=st=UNIX_TIMESTAMP_1~exp=UNIX_TIMESTAMP_2~acl=*/encodings/{SOME_ID}/*~hmac=HMAC_SIGNATURE

Fortunately it seems from the UNIX timestamps included in the token_ak string, which, by the way, is an Akamai token, that the token is long-lived (I can use it at least for 7 days). I grabbed the URL, threw it in curl and saw a .ts file land on disk. Great! Upon opening it, though, nothing really exciting showed up.

Example of a Spotify Wrapped video

Alright, so let’s continue to dig. I restarted the app a couple of times because some of the requests seemed to be cached, and therefore I wasn’t going anywhere when I tried launching the story. And then, I saw this:

Screenshot of mitmproxy inside Windows Subsystem for Linux that shows a request in focus

Bingo! There’s a request to:

https://spclient.wg.spotify.com/campaigns-service/v1/campaigns/wrapped/consumer

This request has a bunch of headers attached to it, which is great, but the most important is Authorization which is just a bearer token that you can grab by inspecting your local Spotify traffic (even from a desktop client). Running a GET request against this endpoint returned something interesting - the content type is application/protobuf. I haven’t worked with ProtoBufs since my foray into the Nest camera internals, so this was an exciting opportunity to re-familiarize myself with the tooling.

Because I didn’t want to write any code, I thought I would leverage protoc to decode the data. If you’re on a Debian system (or inside Ubuntu in WSL), you can run:

sudo apt-get install protobuf-compiler

Once installed, I can download the binary data through curl:

curl --location --request GET 'https://spclient.wg.spotify.com/campaigns-service/v1/campaigns/wrapped/consumer' --header 'Authorization: YOUR_BEARER_TOKEN' --output data.proto

And lastly, I now decode the raw message:

protoc --decode_raw < data.proto

What do you know! The structured response for all Spotify Wrapped scenes:

Example of structured data behind Spotify Wrapped

As it turns out, there is very little video media behind the scenes. Most behaviors are encoded in this ProtoBuf payload. If you are adventurous enough, you can even go as far as reconstruct your own Spotify Wrapped experience.

Conclusion #

Given that this is just structured data behind the scenes, it will remain a puzzle as to why Spotify decided not to enable Wrapped in the browser. However, it seems that the content in the ProtoBuf payload is just enough (including the preview MP3 audio, which is 96kbps and public) to build a custom experience in any environment.